﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Drawing;
using System.Diagnostics;

using CK.Plugin;
using ContribServices;
using CK.Kernel.Config;

namespace GesturePlugin
{
    [Plugin(Id = "{E1FF5C6B-3D8D-4d7f-B4C6-7838AEDFA238}",
        PublicName = "Gesture", Version = "1.0",
        DefaultPluginStatus = ConfigPluginStatus.Manual,
        Categories = new string[] { "Advanced" },
        Description = "Gesture recognizer.")]

    public class GesturesProcessing : IPlugin, IGesturePlugin
    {
        private const double MAX_ANGLE_ARROUND_DIRECTION = 22.5;
        private const double MAX_LEVENSHTEIN_VALUE_ALLOWED = 10; // maximum value of Levenshtein's algorithm

        private int _minimalDistanceOfCapture = 50;  // Precision of catpure in pixels
        private Canvas _canvas;
        private Point _lastPoint;
        private IList _gestureDictionnary;
        private GestureCanva _gestureCanvas;
        private IPointerDeviceDriver _pointerDeviceDriver;
        private string _drawnGesture;

        /// <summary>
        /// Access to the property bag
        /// </summary>
        public IPluginConfigAccessor Config { get; set; }


        /// <summary>
        /// Access to the PointerDeviceDriver
        /// </summary>
        [Service(Requires = ServiceRequirements.MustExistAndRun)]
        public IPointerDeviceDriver PointerDeviceDriver
        {
            get { return _pointerDeviceDriver; }
            set { _pointerDeviceDriver = value; }
        }

        #region IPlugin Membres

        public bool CanStart(out string lastError)
        {
            lastError = "";
            return true;
        }

        public bool Setup(ISetupInfo info)
        {
            return true;
        }

        public void Start()
        {
            Init();
        }
                
        public void Stop()
        {
            _gestureCanvas.Close();
            PointerDeviceDriver.PointerButtonDown -= new PointerDeviceEventHandler(MouseClick);
            PointerDeviceDriver.PointerButtonUp -= new PointerDeviceEventHandler(MouseRelease);
        }

        public void Teardown()
        {
        }

        public void OnConfigurationChanged(IConfigEntry e)
        {
            switch (e.Key)
            {
                case "DefaultPrecision":
                    UpdateMinimalDistanceOfCapture();
                    break;
                case "gestureDictionnary":
                    _gestureDictionnary = (IList)Config.User["gestureDictionnary"];
                    break;
                default:
                    break;
            }
        }

        #endregion

        /// <summary>
        /// Calls when you click right
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MouseClick(object sender, PointerDeviceEventArgs e)
        {
            if (e.ExtraInfo == "Right")
            {
                _canvas.MouseMove += new System.Windows.Input.MouseEventHandler(CaptureGesture);
                _gestureCanvas.CanDraw();
                _lastPoint = System.Windows.Forms.Cursor.Position;
                _gestureCanvas.Show();
            }
        }

        /// <summary>
        /// Calls when the mouseClick is released 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MouseRelease(object sender, PointerDeviceEventArgs e)
        {
            _canvas.MouseMove -= new System.Windows.Input.MouseEventHandler(CaptureGesture);
            if (_drawnGesture != "")
            {
                MatchGesture();
            }
            _drawnGesture = "";
            _gestureCanvas.CantDraw();
            _gestureCanvas.Hide();
        }

        /// <summary>
        /// Initializes variables and defines default gestures
        /// </summary>
        private void Init()
        {
            _gestureCanvas = new GestureCanva();
            PointerDeviceDriver.PointerButtonDown += new PointerDeviceEventHandler(MouseClick);
            PointerDeviceDriver.PointerButtonUp += new PointerDeviceEventHandler(MouseRelease);
            UpdateMinimalDistanceOfCapture();
            _gestureDictionnary = new ArrayList();
            _canvas = _gestureCanvas.canvas;
            _drawnGesture = "";

            #region Add a gesture

            if ((IList)Config.User["gestureDictionnary"] == null)
            {
                Gesture squareGesture =  new Gesture("carre");
                squareGesture.AddPattern("1357");
                squareGesture.AddPattern("3571");
                squareGesture.AddPattern("5713");
                squareGesture.AddPattern("7135");
                squareGesture.AddPattern("3175");
                squareGesture.AddPattern("1753");
                squareGesture.AddPattern("7531");
                squareGesture.AddPattern("5317");

                Gesture leftGesture = new Gesture("left");
                leftGesture.AddPattern("5");

                Gesture rightGesture = new Gesture("right");
                rightGesture.AddPattern("1");

                Gesture downGesture = new Gesture("Down");
                downGesture.AddPattern("7");

                Gesture topGesture = new Gesture("Top");
                topGesture.AddPattern("3");

                Gesture aGesture = new Gesture("A");
                aGesture.AddPattern("28");

                Gesture cGesture = new Gesture("C");
                cGesture.AddPattern("571");

                Gesture vGesture = new Gesture("V");
                vGesture.AddPattern("82");

                Gesture xGesture = new Gesture("X");
                xGesture.AddPattern("638");

                Gesture zGesture = new Gesture("Z");
                zGesture.AddPattern("161");

                Gesture yGesture = new Gesture("Y");
                yGesture.AddPattern("826");

                Gesture wGesture = new Gesture("W");
                wGesture.AddPattern("8282");

                Gesture mGesture = new Gesture("M");
                mGesture.AddPattern("3827");

                Gesture rGesture = new Gesture("R");
                rGesture.AddPattern("31758");
                rGesture.AddPattern("3868");

                Gesture tGesture = new Gesture("T");
                tGesture.AddPattern("17");

                Gesture sGesture = new Gesture("S");
                sGesture.AddPattern("57175");

                _gestureDictionnary.Add(squareGesture);
                _gestureDictionnary.Add(leftGesture);
                _gestureDictionnary.Add(rightGesture);
                _gestureDictionnary.Add(downGesture);
                _gestureDictionnary.Add(topGesture);
                _gestureDictionnary.Add(aGesture);
                _gestureDictionnary.Add(cGesture);
                _gestureDictionnary.Add(vGesture);
                _gestureDictionnary.Add(xGesture);
                _gestureDictionnary.Add(zGesture);
                _gestureDictionnary.Add(yGesture);
                _gestureDictionnary.Add(wGesture);
                _gestureDictionnary.Add(mGesture);
                _gestureDictionnary.Add(rGesture);
                _gestureDictionnary.Add(tGesture);
                _gestureDictionnary.Add(sGesture);

                Config.User.TryAdd("gestureDictionnary", _gestureDictionnary);
            }
            #endregion

            _gestureDictionnary = (IList)Config.User["gestureDictionnary"];
        }

        /// <summary>
        /// Updates the default precision value with the property bag's value
        /// </summary>
        private void UpdateMinimalDistanceOfCapture()
        {
            object o = Config.User["MinimalDistanceOfCapture"];
            _minimalDistanceOfCapture = o == null ? 40 : (int)o;
            _gestureCanvas.GesturePrecision = _minimalDistanceOfCapture;
        }

        /// <summary>
        /// Add a direction into the drawn gesture
        /// </summary>
        /// <remarks>
        /// Duplicate and unknown directions will be filtered out.
        /// </remarks>
        public void AddSegment(MouseGestureDirections direction)
        {
            int lastDirection = 0;
            if(_drawnGesture.Length > 0)
            {
                lastDirection =  (int)_drawnGesture[_drawnGesture.Length - 1] - 48; //we substract 48 because the ascii code of "1" is 49
            }
            
            if (direction != MouseGestureDirections.Unknown && (_drawnGesture == "" || (int)direction != lastDirection))
            {
                Debug.WriteLine(direction);
                _drawnGesture += (int)direction;
            }
        }

        /// <summary>
        /// Adds a segment in a list if the norm is higher or equals to the minimal distance of capture value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CaptureGesture(Object sender, EventArgs e)
        {
            Point point = System.Windows.Forms.Cursor.Position;
            double segmentDistance = GetDistance(_lastPoint, point);
            if (segmentDistance >= _minimalDistanceOfCapture)
            {
                AddSegment(GetDirection(_lastPoint, point));
                _lastPoint = point;
            }
            _gestureCanvas.DrawMouseMoves();
        }

        /// <summary>
        /// Calculates the distance between 2 points
        /// </summary>
        /// <param name="p1">First point</param>
        /// <param name="p2">Second point</param>
        /// <returns>Distance between two points</returns>
        private double GetDistance(Point p1, Point p2)
        {
            int dx = p1.X - p2.X;
            int dy = p1.Y - p2.Y;

            return Math.Sqrt(dx * dx + dy * dy);
        }

        /// <summary>
        /// Recognizes direction between two points
        /// </summary>
        /// <param name="start">Start point of the gesture.</param>
        /// <param name="end">End point of the gesture.</param>
        /// <returns>Gesture direction, if fails returns MouseGestureDirections.Unknown</returns>
        private MouseGestureDirections GetDirection(Point start, Point end)
        {
            //Calcul the value of the distance between the start and the end point on the horizontal axe.
            int deltaX = end.X - start.X;
            //Calcul the value of the distance between the start and the end point on the vertical axe.
            int deltaY = end.Y - start.Y;

            //Distance between the both points
            double length = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);

            double sin = -(deltaY / length); //The referentiel in window is top/down left/right. So we change top/down in down/top with "-" before the sinus.
            double cos = deltaX / length;

            //The angle between the segment and the horizontal axe
            double angle = Math.Acos(Math.Abs(cos)) * 180 / Math.PI;

            //We transform the angle to have value on 360°
            if ((sin >= 0) && (cos < 0))
                angle = 180 - angle;
            else if ((sin < 0) && (cos < 0))
                angle = angle + 180;
            else if ((sin < 0) && (cos >= 0))
                angle = 360 - angle;

            //We define the direction
            if ((angle >= 360 - MAX_ANGLE_ARROUND_DIRECTION) || (angle < MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.Right;
            else if ((angle >= 45 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 45 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.UpRight;
            else if ((angle >= 90 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 90 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.Up;
            else if ((angle >= 135 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 135 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.UpLeft;
            else if ((angle >= 180 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 180 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.Left;
            else if ((angle >= 225 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 225 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.DownLeft;
            else if ((angle >= 270 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 270 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.Down;
            else if ((angle >= 315 - MAX_ANGLE_ARROUND_DIRECTION) && (angle < 315 + MAX_ANGLE_ARROUND_DIRECTION))
                return MouseGestureDirections.DownRight;
            else return MouseGestureDirections.Unknown;
        }

        /// <summary>
        /// Checks if the gesture just drawn is contained into the gestures dictionnary, and launchs an event if it's true
        /// with the recognized gesture as argument.
        /// </summary>
        private void MatchGesture()
        {
            double bestDiff = 100;
            Gesture bestGestureRecognized = new Gesture("No match");

            for (int i = 0 ; i < _gestureDictionnary.Count ; i++)
            {
                Gesture gesture = (Gesture)_gestureDictionnary[i];

                int levenshteinReturnValue = gesture.GetBestLevenshteinValue(_drawnGesture);

                if (levenshteinReturnValue <= MAX_LEVENSHTEIN_VALUE_ALLOWED)
                {
                    if (levenshteinReturnValue < bestDiff)
                    {
                        bestDiff = levenshteinReturnValue;
                        bestGestureRecognized = gesture;
                    }
                }
            }

            Debug.WriteLine(bestGestureRecognized.ToString());

            if (GestureMatched != null)
                GestureMatched(this, new GesturePluginEventArgs(bestGestureRecognized));
        }

        #region IGesturePlugin Members

        /// <summary>
        /// Exposes the gesture dictionnary
        /// </summary>
        public IList GestureDictionnary
        {
            get
            {
                return _gestureDictionnary;
            }
        }

        /// <summary>
        /// Defines the launched event when a gesture is recognized  
        /// </summary>
        public event GesturePluginEventHandler GestureMatched;

        #endregion
    }
}
